#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#include <libxml/parser.h>

#define BUFFER_SIZE 4096
struct this_struct {
  char *node;
  int fd;
};
int idx = 0;
int create_server_socket(int port)
{
  int server_sock;
  struct sockaddr_in server_addr;

  if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("Socket creation failed");
    exit(EXIT_FAILURE);
  }
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);
  if (bind(server_sock,
        (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("Bind failed");
    close(server_sock);
    exit(EXIT_FAILURE);
  }
  if (listen(server_sock, 3) < 0) {
    perror("Listen failed");
    close(server_sock);
    exit(EXIT_FAILURE);
  }
  printf("Listening on port %d...\n", port);
  return server_sock;
}
int send_http_header(int client_sock)
{
  const char *header =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: multipart/x-mixed-replace; boundary=--myboundary\r\n"
    "Cache-Control: no-cache\r\n"
    "Connection: keep-alive\r\n\r\n";
  int ret = send(client_sock, header, strlen(header), 0);
  return ret;
}
int send_http_frame(int client_sock,
    const uint8_t *data, size_t length)
{
  int ret = 0;
  const char *boundary = "--myboundary\r\n";
  const char *content_type = "Content-Type: image/jpeg\r\n\r\n";
  const char *end_boundary = "\r\n";

  ret = send(client_sock, boundary, strlen(boundary), 0);
  if (ret < 1)
    return ret;
  ret = send(client_sock, content_type, strlen(content_type), 0);
  if (ret < 1)
    return ret;
  ret = send(client_sock, data, length, 0);
  if (ret < 1)
    return ret;
  ret = send(client_sock, end_boundary, strlen(end_boundary), 0);
  printf("\rProgress: %d", idx);
  idx++;
  return ret;
}
AVFrame *sws_frame(AVFrame *frame, int pix_fmt, AVCodecContext *ctx)
{
  AVFrame *frame_dst = NULL;
  struct SwsContext *sws_context = NULL;
  int width = frame->width, height = frame->height;
  int ret;

  frame_dst = av_frame_alloc();
  if (!frame_dst) {
    fprintf(stderr, "Could not allocate video frame\n");
    exit(1);
  }

  frame_dst->format = pix_fmt;
  frame_dst->width  = width;
  frame_dst->height = height;

  ret = av_frame_get_buffer(frame_dst, 32);
  if (ret < 0) {
    fprintf(stderr, "Could not allocate frame data.\n");
    exit(1);
  }
  sws_context = sws_getContext(
      ctx->width, ctx->height, ctx->pix_fmt,
      width, height, pix_fmt,
      SWS_BILINEAR, NULL, NULL, NULL);
  sws_scale(sws_context,
      (const uint8_t *const *)frame->data, frame->linesize,
      0, frame->height, frame_dst->data, frame_dst->linesize);
  sws_freeContext(sws_context);
  return frame_dst;
}
void *camera_run(void *ptr)
{
  struct this_struct *self = ptr;
  char *devname = NULL;
  int fd = 0, ret = 0;
  AVFormatContext *format_ctx = NULL;
  AVCodecContext *codec_ctx = NULL;
  const AVCodec *codec = NULL;
  AVPacket packet;
  AVFrame *frame = NULL;
  int video_stream_index = -1;

  const AVCodec *enc_codec = NULL;
  AVCodecContext *enc_codec_ctx = NULL;
  int width = 640, height = 480;

  devname = self->node;
  printf("[DEBUG] %s video name:%s\n", __func__, devname);
  enc_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
  if (!enc_codec) {
    fprintf(stderr, "Codec '%d' not found\n", AV_CODEC_ID_MJPEG);
    exit(1);
  }
  enc_codec_ctx = avcodec_alloc_context3(enc_codec);
  if (!enc_codec_ctx) {
    fprintf(stderr, "Could not allocate video codec context\n");
    exit(1);
  }
  enc_codec_ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
  enc_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
  enc_codec_ctx->width = width;
  enc_codec_ctx->height = height;
  enc_codec_ctx->time_base = (AVRational){1, 25};
  enc_codec_ctx->bit_rate = 400000;
  enc_codec_ctx->framerate = (AVRational){25, 1};
  enc_codec_ctx->gop_size = 10;
  if (enc_codec->id == AV_CODEC_ID_H264)
    av_opt_set(enc_codec_ctx->priv_data, "preset", "slow", 0);
  ret = avcodec_open2(enc_codec_ctx, enc_codec, NULL);
  if (ret < 0) {
    fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
    exit(1);
  }

  if (avformat_open_input(&format_ctx, devname, NULL, NULL) != 0) {
    fprintf(stderr, "Failed to open video input\n");
    return NULL;
  }

  if (avformat_find_stream_info(format_ctx, NULL) < 0) {
    fprintf(stderr, "Failed to find stream information\n");
    return NULL;
  }

  for (int i = 0; i < format_ctx->nb_streams; i++) {
    if (format_ctx->streams[i]->codecpar->codec_type ==
        AVMEDIA_TYPE_VIDEO) {
      video_stream_index = i;
      break;
    }
  }

  if (video_stream_index == -1) {
    fprintf(stderr, "No video stream found\n");
    return NULL;
  }

  codec = avcodec_find_decoder(
      format_ctx->streams[video_stream_index]->codecpar->codec_id);
  if (!codec) {
    fprintf(stderr, "Codec not found\n");
    return NULL;
  }

  codec_ctx = avcodec_alloc_context3(codec);
  if (!codec_ctx || avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_index]->codecpar) < 0) {
    fprintf(stderr, "Failed to open codec\n");
    return NULL;
  }

  if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
    fprintf(stderr, "Failed to open codec\n");
    return NULL;
  }

  frame = av_frame_alloc();
  if (!frame) {
    fprintf(stderr, "Failed to allocate frame\n");
    return NULL;
  }

  while (1) {
    fd = self->fd;
    if (fd < 1) {
      sleep(1);
      continue;
    }
    if (av_read_frame(format_ctx, &packet) < 0)
      break;
    if (packet.stream_index == video_stream_index) {
      if (avcodec_send_packet(codec_ctx, &packet) < 0) {
        av_packet_unref(&packet);
        continue;
      }
      if (avcodec_receive_frame(codec_ctx, frame) == 0) {
        AVFrame *frame_dst = NULL;
        AVPacket *pkt;
        frame_dst = sws_frame(frame, AV_PIX_FMT_YUV420P, codec_ctx);
        //av_frame_free(&frame_dst);
        pkt = av_packet_alloc();
        if (!pkt)
            exit(1);
        if (avcodec_send_frame(enc_codec_ctx, frame_dst) == 0) {
          if (avcodec_receive_packet(enc_codec_ctx, pkt) == 0) {
            fd = self->fd;
            if (fd > 0) {
              ret = send_http_frame(fd, pkt->data, pkt->size);
              if (ret < 1) {
                self->fd = 0;
                close(fd);
              }
            }
          }
        }
        av_frame_free(&frame_dst);
        av_packet_unref(pkt);
      }
    }
    av_packet_unref(&packet);
  }
  avcodec_free_context(&enc_codec_ctx);

  av_frame_free(&frame);
  avcodec_free_context(&codec_ctx);
  avformat_close_input(&format_ctx);
  return NULL;
}
//.build/x86_64/HMjpegVideo/HMjpegVideo 8888 /dev/video0
int main(int argc, char **argv)
{
  int port = 8080;
  char *dev = "/dev/video0";
  //config
  xmlDocPtr doc;
  xmlNodePtr root_node, now;
  xmlChar *xml_char = NULL;
  char *filename;
  if ((argc == 3) && !strcmp(argv[1], "-c")) {
    filename = argv[2];
  } else {
    printf("Usage: %s [OPTION]... [FILE]...\n\
%s\n\
-c\tconfig file\n\
Examples:\n\
\t%s -c config.xml\n", SOFTWARE_NAME, SOFTWARE_NAME, SOFTWARE_NAME);
    exit(1);
  }
  if (access(filename, F_OK) == 0) {
    doc = xmlParseFile(filename);
    printf("[DEBUG]xml_driver__file:%s\n", filename);
  } else {
    printf("[----ERROR] no %s\n", filename);
    exit(1);
   // return NULL;
  }
  root_node = xmlDocGetRootElement(doc);
  now = xmlFirstElementChild(root_node);
  xml_char = xmlGetProp(now, BAD_CAST "port");
  port = atoi((char *)xml_char);
  xml_char = xmlGetProp(now, BAD_CAST "device");
  dev = (char *)xml_char;

  signal(SIGPIPE, SIG_IGN);
  int server_sock, client_sock, ret = 0;
  struct sockaddr_in client_addr;
  socklen_t client_len = sizeof(client_addr);

  avdevice_register_all();

  struct this_struct *self = calloc(1, sizeof(*self));
  self->node = dev;
  pthread_t thread;
  pthread_create(&thread, NULL, camera_run, self);
  pthread_detach(thread);
  server_sock = create_server_socket(port);
  while (1) {
    client_sock = accept(server_sock,
        (struct sockaddr *)&client_addr, &client_len);
    if (client_sock < 0) {
      perror("Accept failed");
      continue;
    }
    ret = send_http_header(client_sock);
    if (ret > 0) {
      self->fd = client_sock;
      printf("Client connected\n");
    } else {
      close(client_sock);
    }
  }
  close(server_sock);
  return 0;
}
